package compiler.my_parser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by szj on 2017/5/30.
 */
public class FirstSetBuilder {

    private Map<Integer, Symbols> symbolMap = new HashMap<>();
    private List<Symbols> symbolArray = new ArrayList<>();
    private boolean runFirstSet = true;

    public FirstSetBuilder() {
        initProductions();
    }

    private void initProductions() {
        List<int[]> rightPortion = new ArrayList<>();
        rightPortion.add(new int[]{SymbolDefinition.EXPR.ordinal()});
        Symbols stmt = new Symbols(SymbolDefinition.STATEMENT.ordinal(), false, rightPortion);
        symbolMap.put(SymbolDefinition.STATEMENT.ordinal(), stmt);
        symbolArray.add(stmt);

        rightPortion = new ArrayList<>();
        rightPortion.add(new int[]{SymbolDefinition.EXPR.ordinal(), SymbolDefinition.PLUS.ordinal(), SymbolDefinition.TERM.ordinal()});
        rightPortion.add(new int[]{SymbolDefinition.TERM.ordinal()});
        Symbols expr = new Symbols(SymbolDefinition.EXPR.ordinal(), false, rightPortion);
        symbolMap.put(SymbolDefinition.EXPR.ordinal(), expr);
        symbolArray.add(expr);

        rightPortion = new ArrayList<>();
        rightPortion.add(new int[]{SymbolDefinition.TERM.ordinal(), SymbolDefinition.TIMES.ordinal(), SymbolDefinition.FACTOR.ordinal()});
        rightPortion.add(new int[]{SymbolDefinition.FACTOR.ordinal()});
        Symbols term = new Symbols(SymbolDefinition.TERM.ordinal(), false, rightPortion);
        symbolMap.put(SymbolDefinition.TERM.ordinal(), term);
        symbolArray.add(term);

        rightPortion = new ArrayList<>();
        rightPortion.add(new int[]{SymbolDefinition.LEFT_PAREN.ordinal(), SymbolDefinition.EXPR.ordinal(), SymbolDefinition.RIGHT_PAREN.ordinal()});
        rightPortion.add(new int[]{SymbolDefinition.NUM_OR_ID.ordinal()});
        Symbols factor = new Symbols(SymbolDefinition.FACTOR.ordinal(), false, rightPortion);
        symbolMap.put(SymbolDefinition.FACTOR.ordinal(), factor);
        symbolArray.add(factor);

        Symbols eoi = new Symbols(SymbolDefinition.EOF.ordinal(), false, null);
        symbolMap.put(SymbolDefinition.EOF.ordinal(), eoi);
        symbolArray.add(eoi);

        Symbols plus = new Symbols(SymbolDefinition.PLUS.ordinal(), false, null);
        symbolMap.put(SymbolDefinition.PLUS.ordinal(), plus);
        symbolArray.add(plus);

        Symbols times = new Symbols(SymbolDefinition.TIMES.ordinal(), false, null);
        symbolMap.put(SymbolDefinition.TIMES.ordinal(), times);
        symbolArray.add(times);

        Symbols lp = new Symbols(SymbolDefinition.LEFT_PAREN.ordinal(), false, null);
        symbolMap.put(SymbolDefinition.LEFT_PAREN.ordinal(), lp);
        symbolArray.add(lp);

        Symbols rp = new Symbols(SymbolDefinition.RIGHT_PAREN.ordinal(), false, null);
        symbolMap.put(SymbolDefinition.RIGHT_PAREN.ordinal(), rp);
        symbolArray.add(rp);

        Symbols num_or_id = new Symbols(SymbolDefinition.NUM_OR_ID.ordinal(), false, null);
        symbolMap.put(SymbolDefinition.NUM_OR_ID.ordinal(), num_or_id);
        symbolArray.add(num_or_id);
    }

    public List<Integer> getFirstSet(int symbol) {
        for (Symbols sym : symbolArray) {
            if (sym.value == symbol) {
                return sym.firstSet;
            }
        }
        return null;
    }

    public void doFirstSet() {
        while (runFirstSet) {
            runFirstSet = false;
            this.symbolArray.forEach(this::doFirstSet);
        }
    }

    public void doFirstSet(Symbols symbol) {
        if (isSymbolTerminals(symbol.value)) {
            if (!symbol.firstSet.contains(symbol.value)) {
                symbol.firstSet.add(symbol.value);
            }
            return;
        }

        if (symbol.rightPortion != null) {
            for (int i = 0; i < symbol.rightPortion.size(); i++) {

                int[] rightPortion = symbol.rightPortion.get(i);
                if (isSymbolTerminals(rightPortion[0]) && !symbol.firstSet.contains(rightPortion[0])) {
                    runFirstSet = true;
                    symbol.firstSet.add(rightPortion[0]);
                    continue;
                }

                int pos = 0;
                Symbols curSymbol;
                do {
                    curSymbol = symbolMap.get(rightPortion[pos]);
                    if (!symbol.firstSet.containsAll(curSymbol.firstSet)) {
                        runFirstSet = true;

                        for (int j = 0; j < curSymbol.firstSet.size(); j++) {
                            if (!symbol.firstSet.contains(curSymbol.firstSet.get(j))) {
                                symbol.firstSet.add(curSymbol.firstSet.get(j));
                            }
                        }
                    }
                    pos++;
                } while (pos < rightPortion.length && curSymbol.isNullable);
            }
        }
    }

    private boolean isSymbolTerminals(int value) {
        return SymbolDefinition.isTerminal(value);
    }

    public boolean isSymbolNullable(int sym) {
        Symbols symbol = symbolMap.get(sym);
        return symbol != null && symbol.isNullable;
    }
}
